from maix import camera, display, image, nn, app, comm
import struct, os
import time
import re
import json

# 导入MQTT客户端类
from mqtt2 import MQTTClient

# 导入七牛云上传模块
from qiniu import QiniuUploader
# 导入服务器数据上报模块
from post import DataUploader

# 导入YOLO11检测功能
from yolo11 import init_detector as init_yolo11_detector, detect_objects as detect_objects_yolo11

report_on = True
APP_CMD_DETECT_RES = 0x02
APP_CMD_PLATE_NUMBER = 0x03  # 添加车牌号命令定义

# 初始化七牛云上传器
qiniu_uploader = QiniuUploader(
    "KgCzxHSGM7pTsrTZbQptzoow42e5IcFOMeFrIoQz",
    "xcloaEa4X3XzqXKDmz_1U1AFtNI1jlbodaHoNOQ4",
    "hongkongidcard",
    "hongkongidcard.wenfree.cn"
)

# 初始化数据上报器
data_uploader = DataUploader()

# 心跳时间记录（使用全局变量）
import builtins
builtins.last_heartbeat_time = time.time()

def encode_objs(objs):
    '''
        encode objs info to bytes body for protocol
        2B x(LE) + 2B y(LE) + 2B w(LE) + 2B h(LE) + 2B idx + 4B score(float) ...
    '''
    body = b''
    for obj in objs:
        body += struct.pack("<hhHHHf", obj.x, obj.y, obj.w, obj.h, obj.class_id, obj.score)
    return body

def encode_plate_number(plate_number):
    '''
    encode plate number to bytes body for protocol
    '''
    return plate_number.encode('utf-8')

# 车牌号统计字典和时间记录
license_plate_stats = {}
last_cleanup_time = time.time()

# 车牌号上传记录字典和时间记录
plate_upload_records = {}

# 判断是否为有效中国车牌号的函数
def is_valid_chinese_license_plate(text):
    """
    判断文本是否为有效的中国车牌号
    中国车牌号规则：
    1. 长度为7-8位
    2. 第一位是汉字（省份简称）
    3. 第二位是英文字母（地区代码）
    4. 其余位是数字或英文字母
    5. 最后可能有一位汉字（如"挂"、"学"等）
    
    Returns:
        tuple: (是否有效, 清理后的车牌号)
    """
    if not text:
        return False, ""
    
    # 去除空格和其他无关字符，只保留中文、英文和数字
    cleaned_text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', text)
    
    # 车牌号长度通常为7-8位
    if len(cleaned_text) < 7 or len(cleaned_text) > 8:
        return False, cleaned_text
    
    # 转换为列表便于处理
    chars = list(cleaned_text)
    
    # 检查第一位是否为汉字（省份简称）
    if not re.match(r'[\u4e00-\u9fa5]', chars[0]):
        return False, cleaned_text
    
    # 检查第二位是否为英文字母（地区代码）
    if not re.match(r'[A-Z]', chars[1]):
        return False, cleaned_text
    
    # 检查其余位是否为数字或英文字母
    for i in range(2, len(chars)):
        if not re.match(r'[0-9A-Z\u4e00-\u9fa5]', chars[i]):
            return False, cleaned_text
    
    # 常见的省份简称
    province_short = ['京', '津', '冀', '晋', '蒙', '辽', '吉', '黑', '沪', '苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤', '桂', '琼', '渝', '川', '贵', '云', '藏', '陕', '甘', '青', '宁', '新']
    
    # 检查省份简称
    if chars[0] not in province_short:
        # 如果第一位不是标准省份简称，但仍是汉字，也可能是特殊车牌
        return False, cleaned_text
    
    return True, cleaned_text

model_path = "car.mud"
if not os.path.exists(model_path):
    model_path = "/root/models/car.mud"
detector = nn.YOLOv5(model=model_path)

# 初始化OCR模型
ocr_model_path = "/root/models/pp_ocr.mud"
if os.path.exists(ocr_model_path):
    ocr = nn.PP_OCR(ocr_model_path)
    # 加载字体
    image.load_font("ppocr", "/maixapp/share/font/ppocr_keys_v1.ttf", size = 20)
    image.set_default_font("ppocr")
else:
    ocr = None

print('input_width', detector.input_width(), detector.input_height(), detector.input_format())
cam = camera.Camera(1920,1080, detector.input_format())
dis = display.Display()

p = comm.CommProtocol(buff_size = 1024)

# 初始化MQTT客户端实例
mqtt_client = None
try:
    # 创建MQTT客户端
    mqtt_client = MQTTClient()
    
    # 连接到MQTT服务器
    if mqtt_client.connect():
        print("✅ MQTT客户端连接成功")
        # 发布初始状态
        mqtt_client.publish_status()
    else:
        print("❌ MQTT客户端连接失败")
        mqtt_client = None
except Exception as e:
    print(f"❌ MQTT客户端初始化失败: {e}")
    mqtt_client = None

# MQTT心跳定时器
last_mqtt_heartbeat = time.time()
mqtt_heartbeat_interval = 30  # 30秒发送一次MQTT心跳

while not app.need_exit():
    try:
        # 每分钟发送一次数据上报心跳
        import builtins
        current_time = time.time()
        if current_time - builtins.last_heartbeat_time >= 60:  # 60秒 = 1分钟
            heartbeat_result = data_uploader.send_heartbeat()
            if heartbeat_result["success"]:
                print("心跳发送成功")
            else:
                print(f"心跳发送失败: {heartbeat_result['error']}")
            builtins.last_heartbeat_time = current_time
        
        # 发送MQTT心跳
        if mqtt_client and current_time - last_mqtt_heartbeat >= mqtt_heartbeat_interval:
            heartbeat_data = {
                "type": "heartbeat",
                "timestamp": int(current_time),
                "device": "MaixCAM Pro"
            }
            mqtt_client.publish_message(json.dumps(heartbeat_data, ensure_ascii=False))
            last_mqtt_heartbeat = current_time
            print(f"💓 发送MQTT心跳 - {time.strftime('%H:%M:%S', time.localtime())}")
        
        # msg = p.get_msg()

        
        img = cam.read()
        
        car_tmp_img_path = '/root/video/car_tmp/'

        # 调用YOLO11进行物体检测并打印结果
        yolo11_results = detect_objects_yolo11(img)
        
        # 添加车辆检测冷却时间控制
        # 全局变量用于跟踪上次上传时间
        if 'last_vehicle_upload_time' not in globals():
            global last_vehicle_upload_time
            last_vehicle_upload_time = 0
        
        current_time = time.time()
        
        if yolo11_results:
            print("🔍 YOLO11检测结果:")
            for i, result in enumerate(yolo11_results):
                print(f"  [{i+1}] {result['label']} - 置信度: {result['score']:.2f}, "
                        f"位置: ({result['x']}, {result['y']}, {result['w']}, {result['h']}), "
                        f"中心点: ({result['center_x_percent']:.4f}, {result['center_y_percent']:.4f})")
                
                # 如果检测结果中有汽车、火车或卡车，将图片保存到临时目录
                vehicle_labels = ['car', 'truck', 'train', '卡车', '火车', '汽车']  # 定义车辆类型标签
                detected_vehicles = [result for result in yolo11_results if result['label'].lower() in vehicle_labels]
                
                if detected_vehicles:
                    # 保存当前分钟的图片（无论是否上传都会覆盖保存）
                    # 创建带时间戳的文件名（精确到分钟，确保每分钟只保留一张图片）
                    timestamp = time.strftime("%Y%m%d_%H%M")
                    filename = f"vehicle_{timestamp}.jpg"
                    # 构建完整的保存路径
                    save_path = os.path.join(car_tmp_img_path, filename)
                    # 确保目录存在
                    os.makedirs(car_tmp_img_path, exist_ok=True)
                    # 保存图片
                    img.save(save_path)
                    print(f"\U0001F697 检测到车辆，图片已保存至: {save_path}")
                    
                    # 做个判断，如果车的中心点，不在 0.3<x<0.7   0.2<y<0.85 
                    # 同时 车的面积小于整个画面面积的50%才 生效 
                    if (0.3 < result['center_x_percent'] < 0.7) and (0.2 < result['center_y_percent'] < 0.85) and (result['w'] * result['h'] < img.width() * img.height() * 0.5):

                        # 检查冷却时间是否已过（60秒）
                        if current_time - last_vehicle_upload_time >= 60:  # 60秒 = 1分钟

                            min_car_img = plate_result = qiniu_uploader.upload_image(save_path, timestamp)
                            print(f"图片已上传至七牛云，文件名为: {min_car_img}")
                            # TODO: 在这里添加您的上传代码
                            # 上传逻辑示例：upload_vehicle_detected(save_path)
                            print("🚗 图片已保存，可以执行上传操作")
                            
                            # 上报数据到服务器
                            data = {
                                "category": "车",
                                "carNo": timestamp,
                                "pimg": "https://ts1.tc.mm.bing.net/th/id/OIP-C.ziryoZfcnYnRzu5gq6moJAHaHa?w=164&h=215&c=8&rs=1&qlt=90&o=6&dpr=1.3&pid=3.1&rm=2",
                                "carimg": min_car_img['url'],  # 使用场景图片的URL
                                "vehicleNo": ""
                            }
                            
                            upload_result = data_uploader.upload_car_info(data)
                            if upload_result["success"]:
                                print("数据上报成功:", upload_result["data"])
                            else:
                                print("数据上报失败:", upload_result["error"])

                            # 上传后重置冷却计时器
                            last_vehicle_upload_time = current_time
                        else:
                            print(f"⏳ 检测到车辆但仍在冷却时间内，距离下次上传还需 {int(60 - (current_time - last_vehicle_upload_time))} 秒")
        # else:
        #     # print("🔍 YOLO11未检测到任何物体")
        #     pass


        objs = detector.detect(img, conf_th = 0.5, iou_th = 0.45)

        if len(objs) > 0 and report_on:
            body = encode_objs(objs)
            p.report(APP_CMD_DETECT_RES, body)

        # 如果检测到对象，裁剪并显示第一个对象
        if len(objs) > 0:
            obj = objs[0]  # 取第一个检测到的对象
            
            # 为了确保裁剪区域不超出图像边界，需要做边界检查
            x = max(0, obj.x)
            y = max(0, obj.y)
            w = min(obj.w, img.width() - x)
            h = min(obj.h, img.height() - y)
            
            # 确保宽度和高度是2的倍数
            w = (w // 2) * 2
            h = (h // 2) * 2  # 修正了这里的错误
            
            # 确保裁剪区域有效
            if w > 0 and h > 0:
                # 裁剪检测框区域
                cropped_img = img.crop(x-20, y-20, w+100, h+50)
                
                # 如果OCR模型可用，对裁剪后的图像进行文字识别
                if ocr is not None:
                    # 为OCR创建一个单独的图像副本，避免格式转换影响原始图像
                    ocr_image = cropped_img.copy()
                    
                    # 对图像进行反色处理，增强OCR识别效果
                    # 将黑色文字转为白色，白色背景转为黑色
                    ocr_image = ocr_image.invert()
                    
                    # 检查并转换图像格式以匹配OCR模型要求
                    if ocr_image.format() != image.Format.FMT_BGR888:
                        ocr_image = ocr_image.to_format(image.Format.FMT_BGR888)
                    
                    # 对裁剪图像进行OCR检测
                    ocr_objs = ocr.detect(ocr_image)
                    # 在原始图像上绘制OCR结果（保持原始图像质量用于显示和上传）
                    for ocr_obj in ocr_objs:
                        points = ocr_obj.box.to_list()
                        cropped_img.draw_keypoints(points, image.COLOR_RED, 4, -1, 1)
                        cropped_img.draw_string(ocr_obj.box.x4, ocr_obj.box.y4, ocr_obj.char_str(), image.COLOR_RED)
                        
                        # 只有当识别结果是有效车牌号时才处理
                        is_valid, cleaned_plate_number = is_valid_chinese_license_plate(ocr_obj.char_str())
                        if is_valid:
                            current_time = time.time()
                            
                            # 每10分钟清理一次过期记录
                            if current_time - last_cleanup_time > 600:  # 600秒 = 10分钟
                                # 清理10分钟前的记录
                                expired_plates = []
                                for plate, (count, timestamp) in license_plate_stats.items():
                                    if current_time - timestamp > 600:
                                        expired_plates.append(plate)
                                
                                # 删除过期记录
                                for plate in expired_plates:
                                    del license_plate_stats[plate]
                                
                                last_cleanup_time = current_time
                            
                            # 获取清理后的车牌号
                            plate_number = cleaned_plate_number
                            
                            # 更新车牌号统计
                            if plate_number in license_plate_stats:
                                # 如果车牌号已存在，增加计数并更新时间戳
                                count, _ = license_plate_stats[plate_number]
                                license_plate_stats[plate_number] = (count + 1, current_time)
                                print(f"车牌号 {plate_number} 再次出现，累计次数: {count + 1}")
                                
                                if count >= 10:
                                    # 如果累计次数达到5次，发送消息给服务器
                                    body = encode_plate_number(plate_number)
                                    p.report(APP_CMD_PLATE_NUMBER, body)
                                    print(f"累计次数达到5次，发送消息给服务器: {plate_number}")

                                    # 上传包含车牌号的图片到七牛云
                                    # 这个车牌上传过一次后，记录一次，10分钟内不再上传
                                    try:
                                        # 保存裁剪后的图像到临时文件（车牌图片）
                                        temp_plate_path = f"/tmp/{plate_number}.jpg"
                                        # 使用原始图像进行保存，以保持图像质量
                                        cropped_img.save(temp_plate_path)
                                        
                                        # 保存整个场景图像到临时文件
                                        temp_scene_path = f"/tmp/{plate_number}ALL.jpg"
                                        img.save(temp_scene_path)
                                        
                                        # 上传车牌图片，使用车牌号作为文件名前缀
                                        plate_result = qiniu_uploader.upload_image(temp_plate_path, plate_number)
                                        # 上传场景图片，使用车牌号+ALL作为文件名前缀
                                        scene_result = qiniu_uploader.upload_image(temp_scene_path, f"{plate_number}ALL")
                                        
                                        if plate_result["success"] and scene_result["success"]:
                                            print(f"车牌图片上传成功: {plate_result['url']}")
                                            print(f"场景图片上传成功: {scene_result['url']}")
                                            # 记录上传时间
                                            plate_upload_records[plate_number] = current_time
                                            
                                            # 上报数据到服务器
                                            data = {
                                                "category": "泥头车",
                                                "carNo": plate_number,
                                                "pimg": plate_result['url'],
                                                "carimg": scene_result['url'],  # 使用场景图片的URL
                                                "vehicleNo": ""
                                            }
                                            
                                            upload_result = data_uploader.upload_car_info(data)
                                            if upload_result["success"]:
                                                print("数据上报成功:", upload_result["data"])
                                            else:
                                                print("数据上报失败:", upload_result["error"])
                                        else:
                                            if not plate_result["success"]:
                                                print(f"车牌图片上传失败: {plate_result['error']}")
                                            if not scene_result["success"]:
                                                print(f"场景图片上传失败: {scene_result['error']}")
                                        
                                        # 删除临时文件
                                        try:
                                            os.remove(temp_plate_path)
                                            os.remove(temp_scene_path)
                                        except:
                                            pass
                                    except Exception as e:
                                        print(f"上传过程中出现错误: {str(e)}")
                            else:
                                # 如果是新车牌号，添加到统计中
                                license_plate_stats[plate_number] = (1, current_time)
                                print(f"新识别到车牌号: {plate_number}")
                                
                                # 检查该车牌号是否在10分钟内已经上传过
                                should_upload = True
                                if plate_number in plate_upload_records:
                                    last_upload_time = plate_upload_records[plate_number]
                                    # 如果距离上次上传不足10分钟(600秒)，则不上传
                                    if current_time - last_upload_time < 600:
                                        should_upload = False
                                        print(f"车牌号 {plate_number} 在10分钟内已上传过，跳过本次上传")
                                
                                # 如果需要上传，则执行上传操作
                                if should_upload:
                                    # 上传包含车牌号的图片到七牛云
                                    try:
                                        # 保存裁剪后的图像到临时文件（车牌图片）
                                        temp_plate_path = f"/tmp/{plate_number}.jpg"
                                        # 使用原始图像进行保存，以保持图像质量
                                        cropped_img.save(temp_plate_path)
                                        
                                        # 保存整个场景图像到临时文件
                                        temp_scene_path = f"/tmp/{plate_number}ALL.jpg"
                                        img.save(temp_scene_path)
                                        
                                        # 上传车牌图片，使用车牌号作为文件名前缀
                                        plate_result = qiniu_uploader.upload_image(temp_plate_path, plate_number)
                                        # 上传场景图片，使用车牌号+ALL作为文件名前缀
                                        scene_result = qiniu_uploader.upload_image(temp_scene_path, f"{plate_number}ALL")
                                        
                                        if plate_result["success"] and scene_result["success"]:
                                            print(f"车牌图片上传成功: {plate_result['url']}")
                                            print(f"场景图片上传成功: {scene_result['url']}")
                                            # 记录上传时间
                                            plate_upload_records[plate_number] = current_time
                                            
                                            # 上报数据到服务器
                                            data = {
                                                "category": "泥头车",
                                                "carNo": plate_number,
                                                "pimg": plate_result['url'],
                                                "carimg": scene_result['url'],  # 使用场景图片的URL
                                                "vehicleNo": ""
                                            }
                                            
                                            upload_result = data_uploader.upload_car_info(data)
                                            if upload_result["success"]:
                                                print("数据上报成功:", upload_result["data"])
                                            else:
                                                print("数据上报失败:", upload_result["error"])
                                        else:
                                            if not plate_result["success"]:
                                                print(f"车牌图片上传失败: {plate_result['error']}")
                                            if not scene_result["success"]:
                                                print(f"场景图片上传失败: {scene_result['error']}")
                                        
                                        # 删除临时文件
                                        try:
                                            os.remove(temp_plate_path)
                                            os.remove(temp_scene_path)
                                        except:
                                            pass
                                    except Exception as e:
                                        print(f"上传过程中出现错误: {str(e)}")
              

                # 将裁剪后的图像调整为640*480大小
                # cropped_img = cropped_img.resize(640, 480)
                
                # 显示裁剪后的图像（可能带有OCR结果）
                # dis.show(cropped_img)
                
                # 暂停1秒
                # time.sleep(1)
            else:
                # 如果裁剪区域无效，显示原始图像
                # 将原始图像调整为640*480大小
                # img = img.resize(640, 480)
                # dis.show(img)
                pass
        else:
            # 如果没有检测到对象，显示原始图像
            # 将原始图像调整为640*480大小
            # img = img.resize(640, 480)
            # dis.show(img)
            pass
            
    except Exception as e:
        print(f"主循环中出现错误: {str(e)}")
        # 发生错误时继续执行循环
        time.sleep(5)
        continue

    

# 清理资源
try:
    cam.__del__()
    dis.__del__()
finally:
    # 断开MQTT连接
    if mqtt_client:
        try:
            mqtt_client.disconnect()
            print("👋 MQTT客户端已断开连接")
        except Exception as e:
            print(f"❌ 断开MQTT连接时出错: {e}")